package game;

import java.awt.Point;
import java.lang.reflect.*;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;

import xml.XMLcommon;
import xml.XMLizeable;

/**
 * <p>This class represents a {@code Piece} from a game of chess. It holds generic information 
 * such as location and must be subclassed to be used.</p>
 * 
 * <p>It is important to note that <tt>getLocation()</tt> returns the internally location 
 * stored within a <tt>Piece</tt>, which does not necessarily correspond with the location 
 * that a <tt>ChessBoard</tt> associates with the <tt>Piece</tt>. This is due to the fact 
 * that <tt>Piece.setLocation()</tt> only alters the data of the <tt>Piece</tt> and has 
 * no knowledge of the <tt>ChessBoard</tt>. <tt>Board.movePiece()</tt> will alter both 
 * records of the location of a <tt>Piece</tt>. In most cases, the latter method is 
 * preferred, but the former should be used when the change in location is temporary.</p>
 * 
 * @author Nicole Waldrum
 * @date January 30, 2010
 * @version 1.0
 *
 * @author Mark Kelly
 * @date February 20, 2010
 * @version 2.0
 */
public abstract class Piece implements MoveChecker, XMLizeable {
	// Class Varaibles
	private static Set<Class<? extends Piece>> pieceTypes = new HashSet<Class<? extends Piece>>();
	
	// Instance Variables
	private String name;
	private Point position;
	private Team team;
	private boolean moved;
	
	/**
	 * <p>Creates a new instance of <tt>Piece</tt>.</p>
	 * 
	 * @param name the name of this <tt>Piece</tt>
	 * @param position the location of this <tt>Piece</tt> on the board
	 * @param team the {@code Team} that this <tt>Piece</tt> is a part member of
	 */
	public Piece(String name, Point position, Team team) {
		this.name = name;
		this.position = position;
		this.team = team;
		this.moved = false;
	}
	
	/**
	 * <p>Creates a new instance of <tt>Piece</tt>.</p>
	 * 
	 * @param position the location of this <tt>Piece</tt> on the board
	 * @param team the <tt>Team</tt> that this <tt>Piece</tt> is a part member of
	 */
	public Piece(Point position, Team team) {
		this.name = team.toLetter() + this.toLetter();
		this.position = position;
		this.team = team;
		this.moved = false;
	}
	
	public Piece (Element piece) {
		team = Team.fromString(XMLcommon.getTextValue(piece, "Team"));
		position = XMLcommon.parseXMLPoint((Element)piece.getElementsByTagName("Location").item(0));
		name = XMLcommon.getTextValue(piece, "Name");
		moved = XMLcommon.getTextValue(piece, "HasMoved").equals("" + true) ? true : false;
	}

	public static void addPieceType(Class<? extends Piece> clazz) {
		pieceTypes.add(clazz);
	}
	
	public static Set<Class<? extends Piece>> getPieceTypes() {
		return pieceTypes;
	}
	
	/** 
	 * @return a letter representing this type of <tt>Piece</tt></p>
	 */
	public abstract String toLetter();

	/**
	 * <p>Return a {@code List<Point>} that must be free in order for this <tt>Piece</tt> to move to a new location.</p>
	 * <p>Assumes that {@code isValidMove()} has been called and returned {@code true}</p>
	 * 
	 * @param dest a <tt>Point</tt> representing the new location
	 * 
	 * @return a {@code List<Point>} with every <tt>Point</tt> that must be free
	 */
	public abstract List<Point> checkMovePattern(Point dest);
	
	/**
	 * <p>Sets the location of the <tt>Piece</tt> on the board</p>
	 * 
	 * @param position the new location for this <tt>Piece</tt>
	 */
	public void setLocation (Point position) {
		this.position = position;
		moved = true;
	}

	/**
	 * <p>Returns the current location of the <tt>Piece</tt> on the board</p>
	 * 
	 * @return the location of the <tt>Piece</tt> on the board
	 */
	public Point getLocation() {
		return position;	
	}
	
	/**
	 * <p>Returns the <tt>Team</tt> that the <tt>Piece</tt> on</p>
	 * 
	 * @return the <tt>Team</tt> that the <tt>Piece</tt> is on
	 */
	public Team getTeam() {
		return this.team;
	}
	
	/**
	 * <p>Returns a reference to this <tt>Piece</tt> as the specified type.</p>
	 * <p>The use of generics allows this single method in <tt>Piece</tt> to function even if new subclasses are added later.</p>
	 * 
	 * @param <T> the new type
	 * @param clazz a {@code Class<T>} representing the new type of the <tt>Piece</tt>
	 * 
	 * @return a reference of Type T to this <tt>Piece</tt>
	 * 
	 * @throws InvocationTargetException if the underlying constructor throws an exception. 
	 * @throws IllegalAccessException if this Constructor object enforces Java language access control and the underlying constructor is inaccessible. 
	 * @throws InstantiationException if the class that declares the underlying constructor represents an abstract class. 
	 * @throws IllegalArgumentException if the number of actual and formal parameters differ; if an unwrapping conversion for primitive arguments fails; or if, after possible unwrapping, a parameter value cannot be converted to the corresponding formal parameter type by a method invocation conversion; if this constructor pertains to an enum type. 
	 * @throws NoSuchMethodException if a matching method is not found
	 * @throws SecurityException
	 * 
	 * TODO catch some exceptions
	 */
	public <T extends Piece> T changeType(Class<T> clazz) throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException {
		// retrieves the constructor for a class of type T
		Constructor<T> c = clazz.getConstructor(String.class, Point.class, Team.class);
		
		// creates a new T from information in this Piece
		return c.newInstance(this.name, this.position, this.team); // creates a new T from information in this Piece
	}
	
	/**
	 * <p>Returns the type of this <tt>Piece</tt> as a {@code String}</p>
	 * 
	 * @return the name of the <tt>Class</tt> of the <tt>Piece</tt>
	 */
	public String getType() {
		return this.getClass().getSimpleName();
	}
	
	/**
	 * <p>Returns the name of this <tt>Piece</tt></p>
	 * 
	 * @return the name of this <tt>Piece</tt>
	 */
	public String getName() {
		return this.name;
	}
	
	/**
	 * <p>Returns whether this <tt>Piece</tt> has moved</p>
	 * 
	 * @return <tt>true</tt> if this <tt>Piece</tt> moved, <tt>false</tt> otherwise
	 */
	public boolean hasMoved() {
		return moved;
	}

	/**
	 * <p>Returns weather or not the <tt>Piece</tt> can move to the given <tt>Point</tt> legally.</p>
	 * 
	 * @param piece the <tt>Piece</tt>
	 * @param point the destination
	 * @return <tt>true</tt> if it is valid, <tt>false</tt> if not
	 */
	public boolean isValidMove(Piece piece, Point point) {
		return !getLocation().equals(point);
	}
	
	public static Piece importFromXML (Element elem) {
		String pieceType = elem.getAttribute("Type");
		Class<?> clazz = null;
		Constructor <?> constructor = null;
		
		try {
			clazz = Class.forName(pieceType);
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		try {
			constructor = clazz.getConstructor(Element.class);
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		try {
			return (Piece) constructor.newInstance(elem);
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return null;
	}
	
	@Override
	public Element toXML (Document doc) {
		Text text;
		Element element, piece;
		
		piece = doc.createElement(Piece.class.getName());
		piece.setAttribute("Type", this.getClass().getName());

		element = doc.createElement("Team");
		text = doc.createTextNode(this.getTeam().toString());
		element.appendChild(text);
		piece.appendChild(element);
		
		element = doc.createElement("Location");
		element.appendChild(XMLcommon.pointToXML(doc, this.getLocation()));
		piece.appendChild(element);
		
		element = doc.createElement("Name");
		text = doc.createTextNode(this.getName());
		element.appendChild(text);
		piece.appendChild(element);
		
		element = doc.createElement("HasMoved");
		text = doc.createTextNode((this.hasMoved() ? "" + true : "" + false));
		element.appendChild(text);
		piece.appendChild(element);
		
		return piece;
	}
}